/*
 *	Copyright 2011 ja4cli
 *
 *	Licensed under the Apache License, Version 2.0 (the "License");
 *	you may not use this file except in compliance with the License.
 *	You may obtain a copy of the License at
 *
 *		http://www.apache.org/licenses/LICENSE-2.0
 *
 *	Unless required by applicable law or agreed to in writing, software
 *	distributed under the License is distributed on an "AS IS" BASIS,
 *	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *	See the License for the specific language governing permissions and
 *	limitations under the License.
 */
package org.skarby.ja4cli.internal;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;

/**
 * Utility class for making reports of structures of classes.<p/>
 * 
 * Useful when trying to figure out how classes are composed and structured.
 * 
 * @author askarby
 */
public class ClassReporting {
	
	/**
	 * Inspects a class for generic typing and annotations upon fields.
	 * @param clazz the class to inspect.
	 * @return the report (as a String).
	 * @throws Exception if something goes wrong (reflection / inspection can be tricky).
	 */
	public static String inspectClass(Class<?> clazz) throws Exception {
		StringBuilder sb = new StringBuilder();
		sb.append(String.format("Class: %s\n", clazz.getName()));
		for (Field field : clazz.getDeclaredFields()) {
			sb.append(String.format("- Field: %s\n", field.getName()));
			Annotation[] annotations = field.getAnnotations();
			if (annotations.length > 0) {
				sb.append("  -- Annotated with:\n");
				for (Annotation each : annotations) {
					Class<? extends Annotation> type = each.annotationType();
					sb.append(String.format("     - %s\n", type.getName()));
					for (Method method : type.getDeclaredMethods()) {
						sb.append(String.format("       - %s: (value: '%s', default: '%s')\n", 
							method.getName(), asString(method.invoke(each)), asString(method.getDefaultValue())));
					}
				}
			}
		}
		for (Type each : clazz.getGenericInterfaces()) {
			ParameterizedType type = (ParameterizedType)each;
			sb.append("- Generics:\n");
			for (Type actual : type.getActualTypeArguments()) {
				sb.append(String.format("  -- %s\n", actual));
			}
		}
		return sb.toString();
	}
	
	/**
	 * Attempts to derive contents of an object as a String, instead of displaying a
	 * class name along with a hashCode()-value. 
	 * @param obj the object to describe contents of as a String.
	 * @return the description of the contents of the object as a String.
	 */
	public static String asString(Object obj) {
		StringBuilder sb = new StringBuilder();
		if (obj.getClass().isArray()) {
			int length = Array.getLength(obj);
			if (length == 0) {
				sb.append("<empty-array>");
			} else {
				for (int i=0; i<length; i++) {
					sb.append(Array.get(obj, i));
					if (i+1 < length) {
						sb.append(", ");
					}
				}
			}
		} else if (obj instanceof Iterable) {
			Iterator<?> it = ((Iterable<?>)obj).iterator();
			while (it.hasNext()) {
				sb.append(it.next());
				if (it.hasNext()) {
					sb.append(", ");
				}
			}
			if (sb.length() == 0) {
				sb.append("<empty-iterable>");
			}
		} else {
			sb.append(obj);
		}
		return sb.toString();
	}
}
